home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2007 December / PCWKCD1207B.iso / Blogowanie poza sfera / Flock 0.9.1.3 stable / flock-0.9.1.3.en-US.win32.exe / flock / components / nsSearchSuggestions.js < prev    next >
Text File  |  2007-10-12  |  27KB  |  841 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Google Suggest Autocomplete Implementation for Firefox.
  15.  *
  16.  * The Initial Developer of the Original Code is Google Inc.
  17.  * Portions created by the Initial Developer are Copyright (C) 2006
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *   Ben Goodger <beng@google.com>
  22.  *   Mike Connor <mconnor@mozilla.com>
  23.  *   Joe Hughes  <joe@retrovirus.com>
  24.  *   Pamela Greene <pamg.bugs@gmail.com>
  25.  *
  26.  * Alternatively, the contents of this file may be used under the terms of
  27.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  28.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  29.  * in which case the provisions of the GPL or the LGPL are applicable instead
  30.  * of those above. If you wish to allow use of your version of this file only
  31.  * under the terms of either the GPL or the LGPL, and not to allow others to
  32.  * use your version of this file under the terms of the MPL, indicate your
  33.  * decision by deleting the provisions above and replace them with the notice
  34.  * and other provisions required by the GPL or the LGPL. If you do not delete
  35.  * the provisions above, a recipient may use your version of this file under
  36.  * the terms of any one of the MPL, the GPL or the LGPL.
  37.  *
  38.  * ***** END LICENSE BLOCK ***** */
  39.  
  40. const SEARCH_RESPONSE_SUGGESTION_JSON = "application/x-suggestions+json";
  41.  
  42. const BROWSER_SUGGEST_PREF = "browser.search.suggest.enabled";
  43. const XPCOM_SHUTDOWN_TOPIC              = "xpcom-shutdown";
  44. const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
  45.  
  46. /**
  47.  * Metadata describing the Web Search suggest mode
  48.  */
  49. const SEARCH_SUGGEST_CONTRACTID =
  50.   "@mozilla.org/autocomplete/search;1?name=search-autocomplete";
  51. const SEARCH_SUGGEST_CLASSNAME = "Remote Search Suggestions";
  52. const SEARCH_SUGGEST_CLASSID =
  53.   Components.ID("{aa892eb4-ffbf-477d-9f9a-06c995ae9f27}");
  54.  
  55. const SEARCH_BUNDLE = "chrome://browser/locale/search.properties";
  56.  
  57. const Cc = Components.classes;
  58. const Ci = Components.interfaces;
  59. const Cr = Components.results;
  60.  
  61. const HTTP_OK                    = 200;
  62. const HTTP_INTERNAL_SERVER_ERROR = 500;
  63. const HTTP_BAD_GATEWAY           = 502;
  64. const HTTP_SERVICE_UNAVAILABLE   = 503;
  65.  
  66. /**
  67.  * SuggestAutoCompleteResult contains the results returned by the Suggest
  68.  * service - it implements nsIAutoCompleteResult and is used by the auto-
  69.  * complete controller to populate the front end.
  70.  * @constructor
  71.  */
  72. function SuggestAutoCompleteResult(searchString,
  73.                                    searchResult,
  74.                                    defaultIndex,
  75.                                    errorDescription,
  76.                                    results,
  77.                                    comments,
  78.                                    formHistoryResult) {
  79.   this._searchString = searchString;
  80.   this._searchResult = searchResult;
  81.   this._defaultIndex = defaultIndex;
  82.   this._errorDescription = errorDescription;
  83.   this._results = results;
  84.   this._comments = comments;
  85.   this._formHistoryResult = formHistoryResult;
  86. }
  87. SuggestAutoCompleteResult.prototype = {
  88.   /**
  89.    * The user's query string
  90.    * @private
  91.    */
  92.   _searchString: "",
  93.  
  94.   /**
  95.    * The result code of this result object, see |get searchResult| for possible
  96.    * values.
  97.    * @private
  98.    */
  99.   _searchResult: 0,
  100.  
  101.   /**
  102.    * The default item that should be entered if none is selected
  103.    * @private
  104.    */
  105.   _defaultIndex: 0,
  106.  
  107.   /**
  108.    * The reason the search failed
  109.    * @private
  110.    */
  111.   _errorDescription: "",
  112.  
  113.   /**
  114.    * The list of words returned by the Suggest Service
  115.    * @private
  116.    */
  117.   _results: [],
  118.  
  119.   /**
  120.    * The list of Comments (number of results - or page titles) returned by the
  121.    * Suggest Service.
  122.    * @private
  123.    */
  124.   _comments: [],
  125.  
  126.   /**
  127.    * A reference to the form history nsIAutocompleteResult that we're wrapping.
  128.    * We use this to forward removeEntryAt calls as needed.
  129.    */
  130.   _formHistoryResult: null,
  131.  
  132.   /**
  133.    * @return the user's query string
  134.    */
  135.   get searchString() {
  136.     return this._searchString;
  137.   },
  138.  
  139.   /**
  140.    * @return the result code of this result object, either:
  141.    *         RESULT_IGNORED   (invalid searchString)
  142.    *         RESULT_FAILURE   (failure)
  143.    *         RESULT_NOMATCH   (no matches found)
  144.    *         RESULT_SUCCESS   (matches found)
  145.    */
  146.   get searchResult() {
  147.     return this._searchResult;
  148.   },
  149.  
  150.   /**
  151.    * @return the default item that should be entered if none is selected
  152.    */
  153.   get defaultIndex() {
  154.     return this._defaultIndex;
  155.   },
  156.  
  157.   /**
  158.    * @return the reason the search failed
  159.    */
  160.   get errorDescription() {
  161.     return this._errorDescription;
  162.   },
  163.  
  164.   /**
  165.    * @return the number of results
  166.    */
  167.   get matchCount() {
  168.     return this._results.length;
  169.   },
  170.  
  171.   /**
  172.    * Retrieves a result
  173.    * @param  index    the index of the result requested
  174.    * @return          the result at the specified index
  175.    */
  176.   getValueAt: function(index) {
  177.     return this._results[index];
  178.   },
  179.  
  180.   /**
  181.    * Retrieves a comment (metadata instance)
  182.    * @param  index    the index of the comment requested
  183.    * @return          the comment at the specified index
  184.    */
  185.   getCommentAt: function(index) {
  186.     return this._comments[index];
  187.   },
  188.  
  189.   /**
  190.    * Retrieves a style hint specific to a particular index.
  191.    * @param  index    the index of the style hint requested
  192.    * @return          the style hint at the specified index
  193.    */
  194.   getStyleAt: function(index) {
  195.     if (!this._comments[index])
  196.       return null;  // not a category label, so no special styling
  197.  
  198.     if (index == 0)
  199.       return "suggestfirst";  // category label on first line of results
  200.  
  201.     return "suggesthint";   // category label on any other line of results
  202.   },
  203.  
  204.   /**
  205.    * Removes a result from the resultset
  206.    * @param  index    the index of the result to remove
  207.    */
  208.   removeValueAt: function(index, removeFromDatabase) {
  209.     // Forward the removeValueAt call to the underlying result if we have one
  210.     // Note: this assumes that the form history results were added to the top
  211.     // of our arrays.
  212.     if (removeFromDatabase && this._formHistoryResult &&
  213.         index < this._formHistoryResult.matchCount) {
  214.       // Delete the history result from the DB
  215.       this._formHistoryResult.removeValueAt(index, true);
  216.     }
  217.     this._results.splice(index, 1);
  218.     this._comments.splice(index, 1);
  219.   },
  220.  
  221.   /**
  222.    * Part of nsISupports implementation.
  223.    * @param   iid     requested interface identifier
  224.    * @return  this object (XPConnect handles the magic of telling the caller that
  225.    *                       we're the type it requested)
  226.    */
  227.   QueryInterface: function(iid) {
  228.     if (!iid.equals(Ci.nsIAutoCompleteResult) &&
  229.         !iid.equals(Ci.nsISupports))
  230.       throw Cr.NS_ERROR_NO_INTERFACE;
  231.     return this;
  232.   }
  233. };
  234.  
  235. /**
  236.  * SuggestAutoComplete is a base class that implements nsIAutoCompleteSearch
  237.  * and can collect results for a given search by using the search URL supplied
  238.  * by the subclass. We do it this way since the AutoCompleteController in
  239.  * Mozilla requires a unique XPCOM Service for every search provider, even if
  240.  * the logic for two providers is identical.
  241.  * @constructor
  242.  */
  243. function SuggestAutoComplete() {
  244.   this._init();
  245. }
  246. SuggestAutoComplete.prototype = {
  247.  
  248.   _init: function() {
  249.     this._addObservers();
  250.     this._loadSuggestPref();
  251.   },
  252.  
  253.   /**
  254.    * this._strings is the string bundle for message internationalization.
  255.    */
  256.   get _strings() {
  257.     if (!this.__strings) {
  258.       var sbs = Cc["@mozilla.org/intl/stringbundle;1"].
  259.                 getService(Ci.nsIStringBundleService);
  260.  
  261.       this.__strings = sbs.createBundle(SEARCH_BUNDLE);
  262.     }
  263.     return this.__strings;
  264.   },
  265.   __strings: null,
  266.  
  267.   /**
  268.    * Search suggestions will be shown if this._suggestEnabled is true.
  269.    */
  270.   _loadSuggestPref: function SAC_loadSuggestPref() {
  271.     var prefService = Cc["@mozilla.org/preferences-service;1"].
  272.                       getService(Ci.nsIPrefBranch);
  273.     this._suggestEnabled = prefService.getBoolPref(BROWSER_SUGGEST_PREF);
  274.   },
  275.   _suggestEnabled: null,
  276.  
  277.   /*************************************************************************
  278.    * Server request backoff implementation fields below
  279.    * These allow us to throttle requests if the server is getting hammered.
  280.    **************************************************************************/
  281.  
  282.   /**
  283.    * This is an array that contains the timestamps (in unixtime) of
  284.    * the last few backoff-triggering errors.
  285.    */
  286.   _serverErrorLog: [],
  287.  
  288.   /**
  289.    * If we receive this number of backoff errors within the amount of time
  290.    * specified by _serverErrorPeriod, then we initiate backoff.
  291.    */
  292.   _maxErrorsBeforeBackoff: 3,
  293.  
  294.   /**
  295.    * If we receive enough consecutive errors (where "enough" is defined by
  296.    * _maxErrorsBeforeBackoff above) within this time period,
  297.    * we trigger the backoff behavior.
  298.    */
  299.   _serverErrorPeriod: 600000,  // 10 minutes in milliseconds
  300.  
  301.   /**
  302.    * If we get another backoff error immediately after timeout, we increase the
  303.    * backoff to (2 x old period) + this value.
  304.    */
  305.   _serverErrorTimeoutIncrement: 600000,  // 10 minutes in milliseconds
  306.  
  307.   /**
  308.    * The current amount of time to wait before trying a server request
  309.    * after receiving a backoff error.
  310.    */
  311.   _serverErrorTimeout: 0,
  312.  
  313.   /**
  314.    * Time (in unixtime) after which we're allowed to try requesting again.
  315.    */
  316.   _nextRequestTime: 0,
  317.  
  318.   /**
  319.    * The last engine we requested against (so that we can tell if the
  320.    * user switched engines).
  321.    */
  322.   _serverErrorEngine: null,
  323.  
  324.   /**
  325.    * The XMLHttpRequest object.
  326.    * @private
  327.    */
  328.   _request: null,
  329.  
  330.   /**
  331.    * The object implementing nsIAutoCompleteObserver that we notify when
  332.    * we have found results
  333.    * @private
  334.    */
  335.   _listener: null,
  336.  
  337.   /**
  338.    * If this is true, we'll integrate form history results with the
  339.    * suggest results.
  340.    */
  341.   _includeFormHistory: true,
  342.  
  343.   /**
  344.    * True if a request for remote suggestions was sent. This is used to
  345.    * differentiate between the "_request is null because the request has
  346.    * already returned a result" and "_request is null because no request was
  347.    * sent" cases.
  348.    */
  349.   _sentSuggestRequest: false,
  350.  
  351.   /**
  352.    * This is the callback for the suggest timeout timer.  If this gets
  353.    * called, it means that we've given up on receiving a reply from the
  354.    * search engine's suggestion server in a timely manner.
  355.    */
  356.   notify: function SAC_notify(timer) {
  357.     // make sure we're still waiting for this response before sending
  358.     if ((timer != this._formHistoryTimer) || !this._listener)
  359.       return;
  360.  
  361.     this._listener.onSearchResult(this, this._formHistoryResult);
  362.     this._formHistoryTimer = null;
  363.     this._reset();
  364.   },
  365.  
  366.   /**
  367.    * This determines how long (in ms) we should wait before giving up on
  368.    * the suggestions and just showing local form history results.
  369.    */
  370.   _suggestionTimeout: 500,
  371.  
  372.   /**
  373.    * This is the callback for that the form history service uses to
  374.    * send us results.
  375.    */
  376.   onSearchResult: function SAC_onSearchResult(search, result) {
  377.     this._formHistoryResult = result;
  378.  
  379.     if (this._request) {
  380.       // We still have a pending request, wait a bit to give it a chance to
  381.       // finish.
  382.       this._formHistoryTimer = Cc["@mozilla.org/timer;1"].
  383.                                createInstance(Ci.nsITimer);
  384.       this._formHistoryTimer.initWithCallback(this, this._suggestionTimeout,
  385.                                               Ci.nsITimer.TYPE_ONE_SHOT);
  386.     } else if (!this._sentSuggestRequest) {
  387.       // We didn't send a request, so just send back the form history results.
  388.       this._listener.onSearchResult(this, this._formHistoryResult);
  389.       this._reset();
  390.     }
  391.   },
  392.  
  393.   /**
  394.    * This is the URI that the last suggest request was sent to.
  395.    */
  396.   _suggestURI: null,
  397.  
  398.   /**
  399.    * Autocomplete results from the form history service get stored here.
  400.    */
  401.   _formHistoryResult: null,
  402.  
  403.   /**
  404.    * This holds the suggest server timeout timer, if applicable.
  405.    */
  406.   _formHistoryTimer: null,
  407.  
  408.   /**
  409.    * This clears all the per-request state.
  410.    */
  411.   _reset: function SAC_reset() {
  412.     // Don't let go of our listener and form history result if the timer is
  413.     // still pending, the timer will call _reset() when it fires.
  414.     if (!this._formHistoryTimer) {
  415.       this._listener = null;
  416.       this._formHistoryResult = null;
  417.     }
  418.     this._request = null;
  419.   },
  420.  
  421.   /**
  422.    * This sends an autocompletion request to the form history service,
  423.    * which will call onSearchResults with the results of the query.
  424.    */
  425.   _startHistorySearch: function SAC_SHSearch(searchString, searchParam, previousResult) {
  426.     var formHistory =
  427.       Cc["@mozilla.org/autocomplete/search;1?name=form-history"].
  428.       createInstance(Ci.nsIAutoCompleteSearch);
  429.     formHistory.startSearch(searchString, searchParam, previousResult, this);
  430.   },
  431.  
  432.   /**
  433.    * Makes a note of the fact that we've recieved a backoff-triggering
  434.    * response, so that we can adjust the backoff behavior appropriately.
  435.    */
  436.   _noteServerError: function SAC__noteServeError() {
  437.     var currentTime = Date.now();
  438.  
  439.     this._serverErrorLog.push(currentTime);
  440.     if (this._serverErrorLog.length > this._maxErrorsBeforeBackoff)
  441.       this._serverErrorLog.shift();
  442.  
  443.     if ((this._serverErrorLog.length == this._maxErrorsBeforeBackoff) &&
  444.         ((currentTime - this._serverErrorLog[0]) < this._serverErrorPeriod)) {
  445.       // increase timeout, and then don't request until timeout is over
  446.       this._serverErrorTimeout = (this._serverErrorTimeout * 2) +
  447.                                  this._serverErrorTimeoutIncrement;
  448.       this._nextRequestTime = currentTime + this._serverErrorTimeout;
  449.     }
  450.   },
  451.  
  452.   /**
  453.    * Resets the backoff behavior; called when we get a successful response.
  454.    */
  455.   _clearServerErrors: function SAC__clearServerErrors() {
  456.     this._serverErrorLog = [];
  457.     this._serverErrorTimeout = 0;
  458.     this._nextRequestTime = 0;
  459.   },
  460.  
  461.   /**
  462.    * This checks whether we should send a server request (i.e. we're not
  463.    * in a error-triggered backoff period.
  464.    *
  465.    * @private
  466.    */
  467.   _okToRequest: function SAC__okToRequest() {
  468.     return Date.now() > this._nextRequestTime;
  469.   },
  470.  
  471.   /**
  472.    * This checks to see if the new search engine is different
  473.    * from the previous one, and if so clears any error state that might
  474.    * have accumulated for the old engine.
  475.    *
  476.    * @param engine The engine that the suggestion request would be sent to.
  477.    * @private
  478.    */
  479.   _checkForEngineSwitch: function SAC__checkForEngineSwitch(engine) {
  480.     if (engine == this._serverErrorEngine)
  481.       return;
  482.  
  483.     // must've switched search providers, clear old errors
  484.     this._serverErrorEngine = engine;
  485.     this._clearServerErrors();
  486.   },
  487.  
  488.   /**
  489.    * This returns true if the status code of the HTTP response
  490.    * represents a backoff-triggering error.
  491.    *
  492.    * @param status  The status code from the HTTP response
  493.    * @private
  494.    */
  495.   _isBackoffError: function SAC__isBackoffError(status) {
  496.     return ((status == HTTP_INTERNAL_SERVER_ERROR) ||
  497.             (status == HTTP_BAD_GATEWAY) ||
  498.             (status == HTTP_SERVICE_UNAVAILABLE));
  499.   },
  500.  
  501.   /**
  502.    * Called when the 'readyState' of the XMLHttpRequest changes. We only care
  503.    * about state 4 (COMPLETED) - handle the response data.
  504.    * @private
  505.    */
  506.   onReadyStateChange: function() {
  507.     // xxx use the real const here
  508.     if (!this._request || this._request.readyState != 4)
  509.       return;
  510.  
  511.     try {
  512.       var status = this._request.status;
  513.     } catch (e) {
  514.       // The XML HttpRequest can throw NS_ERROR_NOT_AVAILABLE.
  515.       return;
  516.     }
  517.  
  518.     if (this._isBackoffError(status)) {
  519.       this._noteServerError();
  520.       return;
  521.     }
  522.  
  523.     var responseText = this._request.responseText;
  524.     if (status != HTTP_OK || responseText == "")
  525.       return;
  526.  
  527.     this._clearServerErrors();
  528.  
  529.     // This is a modified version of Crockford's JSON sanitizer, obtained
  530.     // from http://www.json.org/js.html.
  531.     // This should use built-in functions once bug 340987 is fixed.
  532.     const JSON_STRING = /^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/;
  533.     var sandbox = new Components.utils.Sandbox(this._suggestURI.prePath);
  534.     function parseJSON(aString) {
  535.       try {
  536.         if (JSON_STRING.test(aString))
  537.           return Components.utils.evalInSandbox("(" + aString + ")", sandbox);
  538.       } catch (e) {}
  539.  
  540.       return [];
  541.     };
  542.  
  543.     var serverResults = parseJSON(responseText);
  544.     var searchString = serverResults[0] || "";
  545.     var results = serverResults[1] || [];
  546.  
  547.     var comments = [];  // "comments" column values for suggestions
  548.     var historyResults = [];
  549.     var historyComments = [];
  550.  
  551.     // If form history is enabled and has results, add them to the list.
  552.     if (this._includeFormHistory && this._formHistoryResult &&
  553.         (this._formHistoryResult.searchResult ==
  554.          Ci.nsIAutoCompleteResult.RESULT_SUCCESS)) {
  555.       for (var i = 0; i < this._formHistoryResult.matchCount; ++i) {
  556.         var term = this._formHistoryResult.getValueAt(i);
  557.  
  558.         // we don't want things to appear in both history and suggestions
  559.         var dupIndex = results.indexOf(term);
  560.         if (dupIndex != -1)
  561.           results.splice(dupIndex, 1);
  562.  
  563.         historyResults.push(term);
  564.         historyComments.push("");
  565.       }
  566.     }
  567.  
  568.     // fill out the comment column for the suggestions
  569.     for (var i = 0; i < results.length; ++i)
  570.       comments.push("");
  571.  
  572.     // if we have any suggestions, put a label at the top
  573.     if (comments.length > 0)
  574.       comments[0] = this._strings.GetStringFromName("suggestion_label");
  575.  
  576.     // now put the history results above the suggestions
  577.     var finalResults = historyResults.concat(results);
  578.     var finalComments = historyComments.concat(comments);
  579.  
  580.     // Notify the FE of our new results
  581.     this.onResultsReady(searchString, finalResults, finalComments,
  582.                         this._formHistoryResult);
  583.  
  584.     // Reset our state for next time.
  585.     this._reset();
  586.   },
  587.  
  588.   /**
  589.    * Notifies the front end of new results.
  590.    * @param searchString  the user's query string
  591.    * @param results       an array of results to the search
  592.    * @param comments      an array of metadata corresponding to the results
  593.    * @private
  594.    */
  595.   onResultsReady: function(searchString, results, comments,
  596.                            formHistoryResult) {
  597.     if (this._listener) {
  598.       var result = new SuggestAutoCompleteResult(
  599.           searchString,
  600.           Ci.nsIAutoCompleteResult.RESULT_SUCCESS,
  601.           0,
  602.           "",
  603.           results,
  604.           comments,
  605.           formHistoryResult);
  606.  
  607.       this._listener.onSearchResult(this, result);
  608.  
  609.       // Null out listener to make sure we don't notify it twice, in case our
  610.       // timer callback still hasn't run.
  611.       this._listener = null;
  612.     }
  613.   },
  614.  
  615.   /**
  616.    * Initiates the search result gathering process. Part of
  617.    * nsIAutoCompleteSearch implementation.
  618.    *
  619.    * @param searchString    the user's query string
  620.    * @param searchParam     unused, "an extra parameter"; even though
  621.    *                        this parameter and the next are unused, pass
  622.    *                        them through in case the form history
  623.    *                        service wants them
  624.    * @param previousResult  unused, a client-cached store of the previous
  625.    *                        generated resultset for faster searching.
  626.    * @param listener        object implementing nsIAutoCompleteObserver which
  627.    *                        we notify when results are ready.
  628.    */
  629.   startSearch: function(searchString, searchParam, previousResult, listener) {
  630.     var searchService = Cc["@mozilla.org/browser/search-service;1"].
  631.                         getService(Ci.nsIBrowserSearchService);
  632.  
  633.     // If there's an existing request, stop it. There is no smart filtering
  634.     // here as there is when looking through history/form data because the
  635.     // result set returned by the server is different for every typed value -
  636.     // "ocean breathes" does not return a subset of the results returned for
  637.     // "ocean", for example. This does nothing if there is no current request.
  638.     this.stopSearch();
  639.  
  640.     this._listener = listener;
  641.  
  642.     var engine = searchService.currentEngine;
  643.  
  644.     this._checkForEngineSwitch(engine);
  645.  
  646.     if (!searchString ||
  647.         !this._suggestEnabled ||
  648.         !engine.supportsResponseType(SEARCH_RESPONSE_SUGGESTION_JSON) ||
  649.         !this._okToRequest()) {
  650.       // We have an empty search string (user pressed down arrow to see
  651.       // history), or search suggestions are disabled, or the current engine
  652.       // has no suggest functionality, or we're in backoff mode; so just use
  653.       // local history.
  654.       this._sentSuggestRequest = false;
  655.       this._startHistorySearch(searchString, searchParam, previousResult);
  656.       return;
  657.     }
  658.  
  659.     // Actually do the search
  660.     this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
  661.                     createInstance(Ci.nsIXMLHttpRequest);
  662.     var submission = engine.getSubmission(searchString,
  663.                                           SEARCH_RESPONSE_SUGGESTION_JSON);
  664.     this._suggestURI = submission.uri;
  665.     var method = (submission.postData ? "POST" : "GET");
  666.     this._request.open(method, this._suggestURI.spec, true);
  667.  
  668.     var self = this;
  669.     function onReadyStateChange() {
  670.       self.onReadyStateChange();
  671.     }
  672.     this._request.onreadystatechange = onReadyStateChange;
  673.     this._request.send(submission.postData);
  674.  
  675.     if (this._includeFormHistory) {
  676.       this._sentSuggestRequest = true;
  677.       this._startHistorySearch(searchString, searchParam, previousResult);
  678.     }
  679.   },
  680.  
  681.   /**
  682.    * Ends the search result gathering process. Part of nsIAutoCompleteSearch
  683.    * implementation.
  684.    */
  685.   stopSearch: function() {
  686.     if (this._request) {
  687.       this._request.abort();
  688.       this._reset();
  689.     }
  690.   },
  691.  
  692.   /**
  693.    * nsIObserver
  694.    */
  695.   observe: function SAC_observe(aSubject, aTopic, aData) {
  696.     switch (aTopic) {
  697.       case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
  698.         this._loadSuggestPref();
  699.         break;
  700.       case XPCOM_SHUTDOWN_TOPIC:
  701.         this._removeObservers();
  702.         break;
  703.     }
  704.   },
  705.  
  706.   _addObservers: function SAC_addObservers() {
  707.     var prefService2 = Cc["@mozilla.org/preferences-service;1"].
  708.                        getService(Ci.nsIPrefBranch2);
  709.     prefService2.addObserver(BROWSER_SUGGEST_PREF, this, false);
  710.  
  711.     var os = Cc["@mozilla.org/observer-service;1"].
  712.              getService(Ci.nsIObserverService);
  713.     os.addObserver(this, XPCOM_SHUTDOWN_TOPIC, false);
  714.   },
  715.  
  716.   _removeObservers: function SAC_removeObservers() {
  717.     var prefService2 = Cc["@mozilla.org/preferences-service;1"].
  718.                        getService(Ci.nsIPrefBranch2);
  719.     prefService2.removeObserver(BROWSER_SUGGEST_PREF, this);
  720.  
  721.     var os = Cc["@mozilla.org/observer-service;1"].
  722.              getService(Ci.nsIObserverService);
  723.     os.removeObserver(this, XPCOM_SHUTDOWN_TOPIC);
  724.   },
  725.  
  726.   /**
  727.    * Part of nsISupports implementation.
  728.    * @param   iid     requested interface identifier
  729.    * @return  this object (XPConnect handles the magic of telling the caller that
  730.    *                       we're the type it requested)
  731.    */
  732.   QueryInterface: function(iid) {
  733.     if (!iid.equals(Ci.nsIAutoCompleteSearch) &&
  734.         !iid.equals(Ci.nsIAutoCompleteObserver) &&
  735.         !iid.equals(Ci.nsISupports))
  736.       throw Cr.NS_ERROR_NO_INTERFACE;
  737.     return this;
  738.   }
  739. };
  740.  
  741. /**
  742.  * SearchSuggestAutoComplete is a service implementation that handles suggest
  743.  * results specific to web searches.
  744.  * @constructor
  745.  */
  746. function SearchSuggestAutoComplete() {
  747.   // This calls _init() in the parent class (SuggestAutoComplete) via the
  748.   // prototype, below.
  749.   this._init();
  750. }
  751. SearchSuggestAutoComplete.prototype = {
  752.   __proto__: SuggestAutoComplete.prototype,
  753.   serviceURL: ""
  754. };
  755.  
  756. var gModule = {
  757.   /**
  758.    * Registers all the components supplied by this module. Part of nsIModule
  759.    * implementation.
  760.    * @param componentManager  the XPCOM component manager
  761.    * @param location          the location of the module on disk
  762.    * @param loaderString      opaque loader specific string
  763.    * @param type              loader type being used to load this module
  764.    */
  765.   registerSelf: function(componentManager, location, loaderString, type) {
  766.     if (this._firstTime) {
  767.       this._firstTime = false;
  768.       throw Cr.NS_ERROR_FACTORY_REGISTER_AGAIN;
  769.     }
  770.     componentManager =
  771.       componentManager.QueryInterface(Ci.nsIComponentRegistrar);
  772.  
  773.     for (var key in this.objects) {
  774.       var obj = this.objects[key];
  775.       componentManager.registerFactoryLocation(obj.CID, obj.className, obj.contractID,
  776.                                                location, loaderString, type);
  777.     }
  778.   },
  779.  
  780.   /**
  781.    * Retrieves a Factory for the given ClassID. Part of nsIModule
  782.    * implementation.
  783.    * @param componentManager  the XPCOM component manager
  784.    * @param cid               the ClassID of the object for which a factory
  785.    *                          has been requested
  786.    * @param iid               the IID of the interface requested
  787.    */
  788.   getClassObject: function(componentManager, cid, iid) {
  789.     if (!iid.equals(Ci.nsIFactory))
  790.       throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  791.  
  792.     for (var key in this.objects) {
  793.       if (cid.equals(this.objects[key].CID))
  794.         return this.objects[key].factory;
  795.     }
  796.  
  797.     throw Cr.NS_ERROR_NO_INTERFACE;
  798.   },
  799.  
  800.   /**
  801.    * Create a Factory object that can construct an instance of an object.
  802.    * @param constructor   the constructor used to create the object
  803.    * @private
  804.    */
  805.   _makeFactory: function(constructor) {
  806.     function createInstance(outer, iid) {
  807.       if (outer != null)
  808.         throw Cr.NS_ERROR_NO_AGGREGATION;
  809.       return (new constructor()).QueryInterface(iid);
  810.     }
  811.     return { createInstance: createInstance };
  812.   },
  813.  
  814.   /**
  815.    * Determines whether or not this module can be unloaded.
  816.    * @return returning true indicates that this module can be unloaded.
  817.    */
  818.   canUnload: function(componentManager) {
  819.     return true;
  820.   }
  821. };
  822.  
  823. /**
  824.  * Entry point for registering the components supplied by this JavaScript
  825.  * module.
  826.  * @param componentManager  the XPCOM component manager
  827.  * @param location          the location of this module on disk
  828.  */
  829. function NSGetModule(componentManager, location) {
  830.   // Metadata about the objects this module can construct
  831.   gModule.objects = {
  832.     search: {
  833.       CID: SEARCH_SUGGEST_CLASSID,
  834.       contractID: SEARCH_SUGGEST_CONTRACTID,
  835.       className: SEARCH_SUGGEST_CLASSNAME,
  836.       factory: gModule._makeFactory(SearchSuggestAutoComplete)
  837.     },
  838.   };
  839.   return gModule;
  840. }
  841.